package terra.network;

import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ScheduledFuture;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.channel.ChannelStateEvent;
import org.jboss.netty.channel.ExceptionEvent;
import org.jboss.netty.channel.MessageEvent;
import org.lwjgl.Sys;

import terra.Config;
import terra.Loader;
import terra.managers.DefManager;
import terra.managers.LightManager;
import terra.managers.MapManager;
import terra.managers.ThreadPoolManager;
import terra.model.Arrow;
import terra.model.ClientWeather;
import terra.model.Door;
import terra.model.GameObject;
import terra.model.GameObject.ObjectType;
import terra.model.InventoryState;
import terra.model.ItemClientDef;
import terra.model.LightObject;
import terra.model.MapObject;
import terra.model.Player;
import terra.model.Slot;
import terra.model.WeatherSystem;
import terra.model.effect.BlowEffect;
import terra.model.effect.HitBlockEffect;
import terra.model.effect.HitEffect;
import terra.model.effect.ResurectionEffect;
import terra.network.handler.ClientHandler;
import terra.network.packet.Packet;
import terra.network.packet.Ping;
import terra.network.packet.client.RequestCraftItem;
import terra.network.packet.server.CharInfo;
import terra.network.packet.server.ChatMessage;
import terra.network.packet.server.ChunkInfo;
import terra.network.packet.server.CraftResult;
import terra.network.packet.server.DoorInfo;
import terra.network.packet.server.InventoryInfo;
import terra.network.packet.server.ItemInfo;
import terra.network.packet.server.LightInfo;
import terra.network.packet.server.LoginAnswer;
import terra.network.packet.server.NpcInfo;
import terra.network.packet.server.ObjectDelete;
import terra.network.packet.server.ObjectMove;
import terra.network.packet.server.PlayerInfo;
import terra.network.packet.server.Resurection;
import terra.network.packet.server.StaticInfo;
import terra.network.packet.server.TileInfo;
import terra.network.packet.server.TimeInfo;
import terra.network.packet.server.WaterInfo;
import terra.network.packet.server.WeatherInfo;
import terra.network.packet.server.battle.ArrowInfo;
import terra.network.packet.server.battle.ArrowStop;
import terra.network.packet.server.battle.Attack;
import terra.network.packet.server.battle.Blow;
import terra.network.packet.server.battle.HitInfo;
import terra.slick.GameState;
import terra.slick.MenuState;
import terra.twl.widget.ItemSlot;

public class PlayerHandler extends ClientHandler {

    private static final Logger logger = Logger.getLogger(PlayerHandler.class.getName());
    ScheduledFuture<?> pingTask;
	ConcurrentLinkedQueue<Packet> readHighQueue = new ConcurrentLinkedQueue<Packet>();
	ConcurrentLinkedQueue<Packet> readLowQueue = new ConcurrentLinkedQueue<Packet>();
	Thread readHighTask, readLowTask;
	
    public int ping = 0;
	int count1;
	long time;
	
    public PlayerHandler(){
    	Loader.handler = this;
    }
    
    @Override
    public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {
    	if (e.getMessage() instanceof Packet) {
    		Packet p = (Packet) e.getMessage();
    		if (p.getPriority() == Packet.PRIOR_NORMAL)
    			readQueue.add(p);
    		else if (p.getPriority() == Packet.PRIOR_HIGH)
    			readHighQueue.add((Packet) e.getMessage());
    		else
    			readLowQueue.add((Packet) e.getMessage());
    	}
    }

    @Override
    public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e) {
    	super.channelConnected(ctx, e);
		readHighTask = new Thread(new ReadHighHandler());
		readHighTask.setPriority(Thread.MAX_PRIORITY-3);
		readHighTask.start();
		readLowTask = new Thread(new ReadLowHandler());
		readLowTask.setPriority(Thread.MIN_PRIORITY);
		readLowTask.start();
		
    	readPacketTask.setName("Read Thread - Normal Priority");
    	sendPacketTask.setName("Send Thread");
    	readHighTask.setName("Read Thread - High Priority");
    	readLowTask.setName("Read Thread - Low Priority");
    	pingTask = ThreadPoolManager.getInstance().scheduleGeneralAtFixedRate(new Runnable(){
			@Override
			public void run() {
				send(new Ping(Sys.getTime()));
			}}, 100,200);
    }
    
    @Override
    public void channelDisconnected(ChannelHandlerContext ctx, ChannelStateEvent e) {
    	super.channelDisconnected(ctx, e);
    	readHighTask.interrupt();
    	readLowTask.interrupt();
    }
	@Override
    public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) {
        logger.log(
                Level.WARNING,
                "Unexpected exception from downstream.",
                e.getCause());
    }

	private void readInventoryInfo(InventoryInfo packet) {
		try{
		InventoryState inv = GameState.getInstance().inventory;
		inv.constructable = packet.constructable;
		inv.maxCount = packet.maxCount;
		inv.slotItems = new ItemClientDef[Slot.SLOT_COUNT];
		for(int i = 0; i < Slot.SLOT_COUNT;i++){
			if (packet.slotOIds[i] != -1){
				inv.slotItems[i] = new ItemClientDef(packet.slotOIds[i], packet.slotItemIds[i], 1);	
				System.out.println("SLOT "+i+" : "+ packet.slotOIds[i]+"_"+packet.slotItemIds[i]);
			}
		}
		inv.getInventoryItems().clear();
		for(int i = 0; i < inv.maxCount;i++){
			if (packet.invOIds[i] != -1){
				inv.getInventoryItems().put(packet.invOIds[i],new ItemClientDef(packet.invOIds[i], packet.invItemIds[i], packet.invCounts[i]));
			}
		}
		if (GameState.getInstance().inventoryPanel != null)
			GameState.getInstance().inventoryPanel.update();
		if (GameState.getInstance().characterPanel != null)
			GameState.getInstance().characterPanel.update();
		} catch (Exception e){e.printStackTrace();}
	}

//	private void readStatsInfo(StatsInfo packet) {
//		if (!containsObject(packet.oId))
//			return;
//		Player player = (Player) GameState.getInstance().objects.get(packet.oId);
//		if (player == null)
//			return;
//		if (packet.stat == StatsInfo.STAT_HP)
//			player.setCurrentHp((int) packet.value);
//		else if (packet.stat == StatsInfo.STAT_BREATH)
//			player.setCurrentBreath((int) packet.value);
//		else if (packet.stat == StatsInfo.STAT_EXP)
//			player.setExp((int) packet.value);
//		System.out.println(packet.toString());
//	}

	private void readNpcInfo(NpcInfo packet) {
		GameObject object;
		if (containsObject(packet.oId)) {
			object = GameState.getInstance().objects.get(packet.oId);
		} else {
			object = new GameObject();
			object.setObjectId(packet.oId);	
			GameState.getInstance().objects.put(packet.oId, object);
		}
		object.setXY(packet.x, packet.y);
		object.setId(packet.id);
		object.setW(packet.w);
		object.setH(packet.h);
		object.setType(ObjectType.NPC);

	}

	boolean containsObject(int objectId) {
		return GameState.getInstance().objects.containsKey(objectId);
	}
	
	private void readStaticInfo(StaticInfo packet) {
		GameObject object;
		if (containsObject(packet.oId)) {
			object = GameState.getInstance().objects.get(packet.oId);
			
		} else {
			object  = new GameObject();
			object.setObjectId(packet.oId);	
			GameState.getInstance().objects.put(packet.oId, object);
		}
			object.setXY(packet.x, packet.y);
			object.setId(packet.id);
			object.setW(16);
			object.setH(16);
			if (packet.type == StaticInfo.FLORA)
				object.setType(ObjectType.NPC);
			else if (packet.type == StaticInfo.ITEM)
				object.setType(ObjectType.ITEM);
	}

	private void readChatMessage(ChatMessage packet) {
		String name = "Unknown";
		if (containsObject(packet.oId)){
			GameObject obj = GameState.getInstance().objects.get(packet.oId);
				if (obj instanceof Player){
					name =((Player)obj).getName();
				}
		} else if (packet.oId == -1) name = "System";
		if (GameState.getInstance().chatPanel != null)
			GameState.getInstance().chatPanel.appendRow("default", name+":"+packet.message);
		else 
			readLowQueue.add(packet);
	}

	private void readChunkInfo(ChunkInfo p) {
		for(int i = 0; i < p.coords.length;i++){
			MapObject tile = MapManager.getInstance().getTile(p.x+p.coords[i]%ChunkInfo.size, p.y+p.coords[i]/ChunkInfo.size);
			if (tile == null){
				tile = new MapObject();
				tile.x = p.x+p.coords[i]%ChunkInfo.size;
				tile.y = p.y+p.coords[i]/ChunkInfo.size;
				MapManager.getInstance().setTile(tile);
			}
			tile.mass= p.masses[i];
			tile.hp = p.hps[i];
			tile.id = p.ids[i];
			tile.type = p.types[i];
		}
		ThreadPoolManager.getInstance().scheduleGeneral(new NeighborsCalc(p.x,p.y), 1000L);
		GameState.getInstance().lastChunkSended = System.currentTimeMillis();
	}

	private void readObjectDelete(ObjectDelete packet) {
		GameObject removed = GameState.getInstance().objects.remove(packet.oId);
		if (removed instanceof LightObject){
			LightManager.getInstance().getLights().remove(((LightObject)removed).light);
		}
		if (GameState.getInstance().inventory.getInventoryItems().containsKey(packet.oId))
			GameState.getInstance().inventory.getInventoryItems().remove(packet.oId);
	}

	private void readCharInfo(CharInfo p) {
		Player player;
		if (!containsObject(p.oId)){
			player = new Player();
			player.setObjectId(p.oId);
			player.setName(p.name);
			player.setX(p.x);
			player.setY(p.y);
			player.setW(p.w);
			player.setH(p.h);
			player.setType(ObjectType.PLAYER);
			player.setCurrentHp(p.hp);
			player.setMaxHp(p.maxHp);
			GameState.getInstance().objects.put(p.oId, player);
		}else{
			player = (Player) GameState.getInstance().objects.get(p.oId);
			player.setName(p.name);
			player.setX(p.x);
			player.setY(p.y);
			player.setW(p.w);
			player.setH(p.h);
			player.setCurrentHp(p.hp);
			player.setMaxHp(p.maxHp);
		}
	}

	private void readCurrentTime(TimeInfo packet) {
		GameState.getInstance().lightIntense = packet.lightIntense;
		GameState.getInstance().currentTimeOfDay = packet.time;
	}
	public int count = 0;
	
	private void readTileInfo(TileInfo packet) {
		count++;
		MapObject tile = MapManager.getInstance().getTile(packet.x, packet.y);
		if (tile == null){
			tile = new MapObject();
			tile.x = packet.x;
			tile.y = packet.y;
			MapManager.getInstance().setTile(tile);
			//tile.recalc();
		}
		tile.setType(packet.type);
		tile.mass= packet.mass;
		tile.hp = packet.hp;
		tile.id = packet.id;
	}

	private void readObjectMove(ObjectMove p) {
		if(!GameState.getInstance().objects.containsKey(p.oId))
			return;
		GameObject obj = GameState.getInstance().objects.get(p.oId);
		if (p.num >= obj.getObjectMoveNum()) {
			obj.setXY(p.x,p.y);
			obj.setObjectMoveNum(p.num);
			obj.setHeading(p.heading);
			obj.setLeft(p.left);
			obj.setAnimationCurrent(p.anim_type);
			return;
		}
		System.out.println("lost packet ObjectMove ");
	}

	private void readPlayerInfo(PlayerInfo p) {
		Player player;
		if (GameState.getInstance().player == null){
			player = new Player();
		}else{
			player = GameState.getInstance().player;
		}
		player.setName(p.name);
		player.setObjectId(p.oId);
		player.setX(p.x);
		player.setY(p.y);
		player.setW(p.w);
		player.setH(p.h);
		player.setCon(p.con);
		player.setStr(p.str);
		player.setDex(p.dex);
		player.setCurrentBreath(p.breath);
		player.setMaxBreath(p.maxBreath);
		player.setCurrentHp(p.hp);
		player.setMaxHp(p.maxHp);
		player.setLevel(p.level);
		player.setExp(p.exp);
		player.setAtk(p.atk);
		player.setDef(p.def);
		player.setCritChance(p.critChance);
		player.setCritPower(p.critPower);
		player.setType(ObjectType.PLAYER);
		if (GameState.getInstance().player == null){
			GameState.getInstance().player = player;
			GameState.getInstance().objects.put(p.oId, player);
			GameState.getInstance().updateOffset();
		}
		if (GameState.getInstance().characterPanel != null)
			GameState.getInstance().characterPanel.updateHtml();
		if (GameState.getInstance().hpBar != null)
			GameState.getInstance().hpBar.update();
	}

	private void readLoginAnswer(LoginAnswer packet) {
		if (packet.answer == LoginAnswer.LOGIN_ACCEPT) {
			MenuState.getInstance().enterGame = true;
			return;
		} else if (packet.answer == LoginAnswer.LOGIN_FAIL){
			MenuState.getInstance().errorLabel.setText("Error: wrong password");
		} else if (packet.answer == LoginAnswer.LOGIN_NO_ACC){
			MenuState.getInstance().errorLabel.setText("Error: account not exists");
		} else if (packet.answer == LoginAnswer.LOGIN_CURRENT_IS_ONLINE){
			MenuState.getInstance().errorLabel.setText("Error: account currently is online");
		}
		ThreadPoolManager.getInstance().scheduleGeneral(new Runnable(){

			@Override
			public void run() {
				MenuState.getInstance().errorLabel.setText("");
			}}, 10000);
	}

	
	/*
	 *  * 5 LoginAnswer
		 * 6 ObjectDelete
		 * 7 ObjectMove
		 * 8 ChatMessage
		 * 9 PlayerInfo
		 * 10 CharInfo
		 * 11 StaticInfo
		 * 12 TimeInfo
		 * 13 ChunkInfo
		 * 14 NpcInfo
		 * 15 ItemInfo
		 * 16 TileInfo
		 */
	@Override
	public void handlePacket(Packet packet) {
    	switch(packet.getId()) {
		case 0:
    		ping = (int) (Sys.getTime() -((Ping) packet).time);
    		break;
    	case 5:
    		readLoginAnswer((LoginAnswer) packet);
    		break;
    	case 6:
    		readObjectDelete((ObjectDelete) packet);
    		break;
    	case 7:
    		readObjectMove((ObjectMove) packet);
    		break;
    	case 8:
    		readChatMessage((ChatMessage) packet);
    		break;
    	case 9:
    		readPlayerInfo((PlayerInfo) packet);
    		break;	    		
    	case 10:
    		readCharInfo((CharInfo) packet);
    		break;
    	case 11:
    		readStaticInfo((StaticInfo) packet);
    		break;
    	case 12:
    		readCurrentTime((TimeInfo) packet);
    		break;
    	case 13:
    		readChunkInfo((ChunkInfo) packet);
    		break;	
    	case 14:
    		readNpcInfo((NpcInfo) packet);
    		break;	
    	case 15:
    		readItemInfo((ItemInfo)packet);
    		return;
    	case 16:
    		readTileInfo((TileInfo) packet);
    		break;
    	case 17:
    		readInventoryInfo((InventoryInfo) packet);
    		break;
//    	case 20:
//    		readStatsInfo((StatsInfo) packet);
//    		break; 		
    	case 21:
    		readAttack((Attack) packet);
    		break; 
    	case 22:
    		readArrowInfo((ArrowInfo) packet);
    		break; 
    	case 23:
    		readArrowStop((ArrowStop) packet);
    		break; 
    	case 25:
    		readResurection((Resurection) packet);
    		break; 
    	case 27:
    		readCraftResult((CraftResult) packet);
    		break;
    	case 28:
    		readHitInfo((HitInfo) packet);
    		break;
    	case 29:
    		readLightInfo((LightInfo) packet);
    		break;
    	case 30:
    		readWaterInfo((WaterInfo) packet);
    		break;
    	case 31:
    		readBlow((Blow) packet);
    		break;
    	case 32:
    		readWeatherInfo((WeatherInfo) packet);
    		break;
    	case 33:
    		readDoorInfo((DoorInfo) packet);
    		break;
    	}
	}

	private void readDoorInfo(DoorInfo packet) {
		System.out.println(packet);
		if (containsObject(packet.oId)){
			Door door = (Door) GameState.getInstance().objects.get(packet.oId);
			door.opened = packet.open;
		} else {
			Door door = new Door();
			door.setType(ObjectType.DOOR);
			door.opened = packet.open;
			door.setX(packet.x);
			door.setY(packet.y);
			door.setObjectId(packet.oId);
			door.setId(packet.id);
			GameState.getInstance().objects.put(door.getObjectId(), door);
		}
	}

	private void readWeatherInfo(WeatherInfo packet) {
		System.out.println("Weather " + DefManager.weatherDefs.get(packet.id).name);
		if (WeatherSystem.current == null){
			ClientWeather c  = new ClientWeather(DefManager.weatherDefs.get(packet.id));
			c.intensity = packet.intensity;
			c.lightIntense = packet.light;
			WeatherSystem.setCurrent(c);

		} else {
			ClientWeather c  = new ClientWeather(DefManager.weatherDefs.get(packet.id));
			c.intensity = packet.intensity;
			c.lightIntense = packet.light;
			WeatherSystem.setNext(c);
		}
	}

	private void readBlow(Blow p) {
		BlowEffect eff = new BlowEffect(p.x,p.y,p.r,p.g,p.b,p.radius,p.time);
		GameState.getInstance().effects.add(eff);
	}

	private void readWaterInfo(WaterInfo p) {
		for(int i = 0; i < p.coordsx.length;i++){
			MapObject tile = MapManager.getInstance().getTile(p.coordsx[i], p.coordsy[i]);
			if (tile == null){
				tile = new MapObject();
				tile.x = p.coordsx[i];
				tile.y = p.coordsy[i];
				
				MapManager.getInstance().setTile(tile);
			}
			tile.mass= p.masses[i];
			tile.hp = 0;
			tile.id = -2;
			tile.type = MapObject.TYPE_WATER;
		}
	}

	private void readLightInfo(LightInfo packet) {
		//System.out.println("lightinfo!");
		LightObject obj;
		if (containsObject(packet.oId)){
			obj = (LightObject) GameState.getInstance().objects.get(packet.oId);
		} else {
			obj = new LightObject();
			obj.setObjectId(packet.oId);
			obj.setId(packet.id);
			obj.setType(ObjectType.LIGHT);
			obj.setRadius(packet.radius);
			obj.init();
			GameState.getInstance().objects.put(packet.oId, obj);
		}
		obj.setLights(packet.lights == 1 ? true: false);
		obj.setX(packet.x);
		obj.setY(packet.y);
		obj.setRadius(packet.radius);
	}

	private void readHitInfo(HitInfo packet) {
		if (packet.isCrit == HitInfo.BLOCK){
			if (MapManager.getInstance().getTile(packet.attackoId/Config.tileSize, packet.targetOId/Config.tileSize) != null){
				HitBlockEffect eff = new HitBlockEffect(MapManager.getInstance().getTile(packet.attackoId/Config.tileSize, packet.targetOId/Config.tileSize),packet.damage);
				GameState.getInstance().effects.add(eff);
			}
		} else{
			if (GameState.getInstance().objects.get(packet.targetOId)== null)
				return;
			HitEffect eff = new HitEffect(GameState.getInstance().objects.get(packet.targetOId),packet.damage,packet.isCrit);
			GameState.getInstance().effects.add(eff);
		}
	}

	private void readCraftResult(CraftResult p) {
		for(int i = 0; i < RequestCraftItem.COUNT;i++){
			if (p.itemIds[i] == -1)
				continue;
			ItemSlot slot = GameState.getInstance().craftPanel.getSlotByOId(p.itemOids[i]);
			if (slot != null){
				slot.def.setCount(p.itemCounts[i]);
				if (slot.def.getCount() == 0)
					slot.reset();
			}
		}
	}

	private void readResurection(Resurection packet) {
		if (packet.oId == GameState.getInstance().player.getObjectId()){
			// TODO effect resurection
		} else {
			// TODO effect resurection
		}
		GameState.getInstance().effects.add(new ResurectionEffect(packet.oId));
	}

	private void readArrowStop(ArrowStop packet) {
		if (!containsObject(packet.oId))
			return;
		Arrow arrow = (Arrow) GameState.getInstance().objects.get(packet.oId);
		arrow.stopped = true;
		arrow.time = System.currentTimeMillis();
	}

	private void readArrowInfo(ArrowInfo packet) {
		if (containsObject(packet.oId))
			return;
		Arrow arrow = new Arrow();
		arrow.setXY(packet.x, packet.y);
		arrow.setW(packet.w);
		arrow.setH(packet.h);
		arrow.setObjectId(packet.oId);
		arrow.setSGA(packet.speed,packet.gravity,packet.angle);
		GameState.getInstance().objects.put(packet.oId, arrow);
	}

	private void readAttack(Attack packet) {
		if (containsObject(packet.oId))
			GameState.getInstance().objects.get(packet.oId).attackAnimation();
	}

	private void readItemInfo(ItemInfo packet) {
//		try{
//		InventoryState inv = GameState.getInstance().inventory;
//		if (packet.container == ItemInfo.INVENTORY) {
//			int pos = inv.containsItem(packet.oId);
//			if (pos != -1){
//				if (packet.id == -1) {
//					System.out.println("remove "+ pos);
//					inv.inventoryItems.set(pos, null);
//					//GameState.getInstance().inv.updateItem(pos,null,packet.container);
//				} else {
//					inv.inventoryItems.get(pos).setCount(packet.count);
//					//GameState.getInstance().inv.updateItem(pos,inv.inventoryItems.get(pos),packet.container);
//				}
//			} else {
//				inv.inventoryItems.add(new ItemClientDef(packet.oId,packet.id,packet.count));
//				//GameState.getInstance().inv.updateItem(inv.containsItem(packet.oId),inv.inventoryItems.get(inv.containsItem(packet.oId)),packet.container);
//			}
//		} else {
//			if (packet.id == -1) {
//				inv.slotItems[packet.position] = null;
//				///GameState.getInstance().inv.updateItem(packet.position,null,packet.container);
//			} else {
//				ItemClientDef def = new ItemClientDef(packet.oId,packet.id,packet.count);
//				inv.slotItems[packet.position] = def;
//				//GameState.getInstance().inv.updateItem(packet.position,def,packet.container);
//			}
//
//		}
//		} catch(Exception e){e.printStackTrace();}
//		//System.out.println(packet.toString());
//		//GameState.getInstance().inv.fullInventoryUpdate();
	}
	
	class ReadHighHandler implements Runnable {
		@Override
		public void run() {
			while(getChannel().isConnected()){
				while(!readHighQueue.isEmpty())
					handlePacket(readHighQueue.poll());
				try {
					Thread.sleep(0,50);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}
    }
	
	class ReadLowHandler implements Runnable {

		@Override
		public void run() {
			try {
				while(getChannel().isConnected()){
					while(!readLowQueue.isEmpty()){
						handlePacket(readLowQueue.poll());
						Thread.sleep(0,500);
					}
					Thread.sleep(0,500);
				}
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
    }	
}
